package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.aspect.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributeLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: xie.zh
 * @create: 2021.03.20   01:08
 * @version: 1.0
 */
@Service
public class IndexService {
    @Autowired
    private GmallPmsClient pmsClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private DistributeLock distributeLock;
    @Autowired
    private RedissonClient redissonClient;

    private final String KEY_PREFIX = "index:cates:";
    private final String LOCK_PREFIX = "index:cates:lock:";

    public List<CategoryEntity> queryLevel1Categories() {
        ResponseVo<List<CategoryEntity>> categoryEntityResponseVo = pmsClient.getCategoryEntities(0L);
        return categoryEntityResponseVo.getData();
    }

    @GmallCache(prefix = KEY_PREFIX, timeout = 259200, random = 7200, lock = LOCK_PREFIX)
    public List<CategoryEntity> queryLvl2CategoriesWithSub2(Long pid) {
        //查询数据库
        ResponseVo<List<CategoryEntity>> categoriesWithSubResponseVo = pmsClient.queryCategoriesWithSub(pid);
        List<CategoryEntity> categoryEntities = categoriesWithSubResponseVo.getData();
        return categoryEntities;
    }


    public List<CategoryEntity> queryLvl2CategoriesWithSub(Long pid) {

        //先走redis缓存 如果缓存没找到再找mysql
        String json = redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseArray(json, CategoryEntity.class);
        }

        //为了防止缓存击穿添加分布式锁
        RLock fairLock = this.redissonClient.getFairLock(LOCK_PREFIX + pid);
        fairLock.lock();

        try {
            //在获取分布式锁过程中可能已经有其他请求将数据放入缓存 此时应该再次确认缓存中是否存在 如果存在直接返回
            String json2 = redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseArray(json2, CategoryEntity.class);
            }
            //查询数据库
            ResponseVo<List<CategoryEntity>> categoriesWithSubResponseVo = pmsClient.queryCategoriesWithSub(pid);
            List<CategoryEntity> categoryEntities = categoriesWithSubResponseVo.getData();
            //防止缓存穿透 查询结构为空也存入redis
            if (!CollectionUtils.isEmpty(categoryEntities)) {
                redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 5, TimeUnit.DAYS);
            }
            //防止缓存雪崩 随机失效时间
            redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities),
                    180 + new Random().nextInt(30),
                    TimeUnit.DAYS);
            return categoryEntities;
        } finally {
            fairLock.unlock();
        }
    }

    public synchronized void testLock() {
        String numString = this.redisTemplate.opsForValue().get("num");
        if (StringUtils.isEmpty(numString)) {
            return;
        }
        int num = Integer.parseInt(numString);
        this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
    }

    public void testLock2() {
        //获取锁
        String uuid = UUID.randomUUID().toString();
        Boolean lock = distributeLock.tryLock("lock", uuid, 30);
        if (lock) {
            String numString = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(numString)) {
                return;
            }
            int num = Integer.parseInt(numString);
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
//            this.subLock(uuid);
            try {
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        distributeLock.unLock("lock", uuid);
    }

    public void testLock3() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String numString = this.redisTemplate.opsForValue().get("num");
        if (StringUtils.isBlank(numString)) {
            return;
        }
        int num = Integer.parseInt(numString);
        this.redisTemplate.opsForValue().set("num", String.valueOf(++num));

        lock.unlock();
    }


    public void subLock(String uuid) {
        this.distributeLock.tryLock("lock", uuid, 30);
        System.out.println("测试可重入锁");
        this.distributeLock.unLock("lock", uuid);
    }

    public void testRead() {
        RReadWriteLock wrLock = this.redissonClient.getReadWriteLock("wrLock");
        wrLock.readLock().lock(10, TimeUnit.SECONDS);
        System.out.println("=================");
    }

    public void testWrite() {
        RReadWriteLock wrLock = this.redissonClient.getReadWriteLock("wrLock");
        wrLock.writeLock().lock(10, TimeUnit.SECONDS);
        System.out.println("=================");
    }


    public void testLatch() {
        RCountDownLatch latch = this.redissonClient.getCountDownLatch("latch");
        latch.trySetCount(6);
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void testcountDown() {
        RCountDownLatch latch = this.redissonClient.getCountDownLatch("latch");
        latch.countDown();
    }
}
